#include "mem.h"
#include "counter.h"
#define MEMORY_SIZE (100 * 1024 * 1024)
#define MEMORY_BASE 0x80000000ull
uint64_t mem[MEMORY_SIZE / sizeof(uint64_t)];
extern Counter cycle;
static const uint32_t img[] = {
    0x00000297, // auipc t0,0
    0x0002b823, // sd  zero,16(t0)
    0x0102b503, // ld  a0,16(t0)
    0x00100073, // ebreak (used as nemu_trap)
    0xdeadbeef, // some data
};
void print_cpt();
Counter load_out_of_bound("load_out_of_bound");
uint8_t *guest_to_host(uint64_t paddr, bool isWrite)
{
    static uint64_t temp;
    temp = -1ull;
    if (paddr < MEMORY_BASE || (paddr - MEMORY_BASE) >= MEMORY_SIZE)
    {
        LOG("Memory %s addr 0x%08lx out of bound", isWrite ? "Write" : "Read", paddr);
        if (isWrite)
        {
            Verilated::gotFinish(true);
            Verilated::gotError(true);
            print_cpt();
        }
        else
        {
            load_out_of_bound++;
        }
        return (uint8_t *)&temp;
    }
    return (uint8_t *)((uintptr_t)mem + paddr - MEMORY_BASE);
}

FILE *uart_fp = NULL;
void deinit_uart()
{
    fclose(uart_fp);
}
void init_uart()
{
    uart_fp = fopen("uart.log", "w+");
    atexit(deinit_uart);
}
void write_itcm(const char *img_file, size_t size)
{
    FILE *fp = fopen(img_file, "w+");
    assert(fp);
    for (size_t i = 0; i < (size+3) / 4; i++)
    {
        fprintf(fp, "0x%08x\n", ((uint32_t *)mem)[i]);
    }
    fclose(fp);
}
void write_dtcm(const char *img_file, size_t size)
{
    FILE *fp = fopen(img_file, "w+");
    assert(fp);
    for (size_t i = 0; i < (size+7) / 8; i++)
    {
        fprintf(fp, "0x%016lx\n", ((uint64_t *)mem)[i]);
    }
    fclose(fp);
}
const char *itcm_file = "./itcm_image.txt";
const char *dtcm_file = "./dtcm_image.txt";
void init_mem(char *img_file)
{
    init_uart();
    if (img_file == NULL)
    {
        LOG("No image is given. Use the default build-in image.\n");
        memcpy(mem, img, sizeof(img));
        write_itcm(itcm_file, sizeof(img));
        write_dtcm(dtcm_file, sizeof(img));
        return;
    }
    else
    {
        LOG("load img : %s", img_file);
    }
    FILE *fp = fopen(img_file, "rb");
    assert(fp);

    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);

    LOG("The image is %s, size = %ld", img_file, size);

    fseek(fp, 0, SEEK_SET);
    int ret = fread(mem, size, 1, fp);
    assert(ret == 1);

    fclose(fp);
    write_itcm(itcm_file, size);
    write_dtcm(dtcm_file, size);
}
#ifdef __cplusplus
extern "C"
{
#endif
    int iram_read(long long pc, int offset)
    {
        uint32_t *p = (uint32_t *)guest_to_host(pc + offset * 4, false);
        // if (Verilated::gotError())
        //     LOG("IR 0x%08lx => 0x%08x at %ld", pc + offset * 4, *p, Verilated::time());
        return *p;
    }
    long long mem_read(long long addr, char size)
    {
        if (addr == 0xa0000048)
        {
            return cycle;
        }
        uint64_t ret_val = 0;
        switch (size)
        {
        case 0b1:
            ret_val = *(uint8_t *)guest_to_host(addr, false);
            break;
        case 0b10:
            ret_val = *(uint16_t *)guest_to_host(addr, false);
            break;
        case 0b100:
            ret_val = *(uint32_t *)guest_to_host(addr, false);
            break;
        case 0b1000:
            ret_val = *(uint64_t *)guest_to_host(addr, false);
            break;
        default:

            break;
        }
        // if (Verilated::gotError())
        //     LOG("MR 0x%08lx => 0x%08x at %ld", addr, ret_val, Verilated::time());
        return ret_val;
    }
    void mem_write(long long addr, char size, long long data)
    {
        if (addr == 0xa00003f8)
        {
            fprintf(uart_fp, "%c", (char)data);
            fflush(uart_fp);
            return;
        }
        switch (size)
        {
        case 0b1:
            *(uint8_t *)guest_to_host(addr, true) = data;
            break;
        case 0b10:
            *(uint16_t *)guest_to_host(addr, true) = data;
            break;
        case 0b100:
            *(uint32_t *)guest_to_host(addr, true) = data;
            break;
        case 0b1000:
            *(uint64_t *)guest_to_host(addr, true) = data;
            break;
        default:
            break;
        }
        // if (Verilated::gotError())
        //     LOG("MW 0x%08lx[%d] => 0x%08x at %ld", addr, size, data, Verilated::time());
        return;
    }

#ifdef __cplusplus
}
#endif